﻿Imports System.Reflection
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized
Imports System.Threading.Tasks
Imports System.Threading

''' <summary>
''' プロパティ書き込むクラス
''' </summary>
''' <remarks></remarks>
Public Class PropertyWriter

    ''' <summary>
    ''' 結合条件が一致する親インスタンスと結合させます
    ''' 結合対象が存在しない場合はNothingが代入されます
    ''' </summary>
    ''' <typeparam name="MyT"></typeparam>
    ''' <typeparam name="ParentT"></typeparam>
    ''' <param name="myList"></param>
    ''' <param name="parentList"></param>
    ''' <param name="parentProperty"></param>
    ''' <remarks></remarks>
    Public Shared Sub SetParentOrNothing(Of MyT, ParentT)(ByVal myList As IEnumerable(Of MyT), ByVal parentList As IEnumerable(Of ParentT), ByVal parentProperty As String)
        '結合条件を取得します
        Dim parentProp As PropertyInfo = PropertyReader.ReadParentPropertyInfo(GetType(MyT), GetType(ParentT), parentProperty)
        Dim rels As List(Of ParentRelationColumnAttribute) = ColumnAttributeReader.ReadParentRelationColumns(parentProp).ToList 'RelationAttributeReader.Read(ParentProp).ToList

        Parallel.ForEach(Of MyT)(myList, Sub(myItem As MyT)
                                             Dim isMatch As Boolean = False

                                             For Each yourItem As ParentT In parentList
                                                 Dim m As MyT = myItem
                                                 Dim y As ParentT = yourItem

                                                 '結合条件が一致しなければ無視
                                                 If (From el As ParentRelationColumnAttribute In rels Where el.CanJoin(m, y, parentProperty) = False).Any Then Continue For

                                                 parentProp.SetValue(m, y, Nothing)
                                                 isMatch = True
                                                 Exit For
                                             Next

                                             '結合するものがない場合、Nothingで初期化
                                             If isMatch = False Then parentProp.SetValue(myItem, Nothing, Nothing)
                                         End Sub)
    End Sub

    ''' <summary>
    ''' 結合条件が一致する子インスタンスを追加します
    ''' 結合処理を行う前に、子格納リストは初期化されます
    ''' </summary>
    ''' <typeparam name="MyT"></typeparam>
    ''' <typeparam name="ChildT"></typeparam>
    ''' <param name="myList"></param>
    ''' <param name="childList"></param>
    ''' <param name="childProeprty"></param>
    ''' <remarks></remarks>
    Public Shared Sub ClearAndAddChild(Of MyT, ChildT)(ByVal myList As IEnumerable(Of MyT), ByVal childList As IEnumerable(Of ChildT), ByVal childProeprty As String)
        Dim childProp As PropertyInfo = PropertyReader.ReadChildPropertyInfo(GetType(MyT), GetType(ChildT), childProeprty)
        Dim rels As List(Of ChildRelation) = (From el In GetType(MyT).ToTable.ChildRelations Where el.CollectionProperty.Equals(childProp)).ToList

        Dim fn = Sub(myItem As MyT)
                     CollectionInitializer.Init(Of MyT, ChildT)(myItem)
                     Dim lst = childProp.GetValue(myItem, Nothing)

                     For Each yourItem In childList
                         Dim m As MyT = myItem
                         Dim y As ChildT = yourItem
                         If (From el In rels Where el.CanJoin(m, y) = False).Any Then Continue For
                         lst.Add(yourItem)
                     Next
                 End Sub

        '並列処理
        Parallel.ForEach(Of MyT)(myList, fn)
    End Sub

    ''' <summary>
    ''' プロパティに値を書き込みます
    ''' </summary>
    ''' <param name="prop"></param>
    ''' <param name="model"></param>
    ''' <param name="val"></param>
    ''' <remarks></remarks>
    Public Shared Sub SetValue(ByVal prop As PropertyInfo, ByVal model As Object, ByVal val As Object)
#If DEBUG Then
        If prop Is Nothing Then Throw New ArgumentNullException("prop")
        If model Is Nothing Then Throw New ArgumentNullException("model")
#End If
        Try
            'NULLの場合はNothingを代入
            If IsDBNull(val) Then
                prop.SetValue(model, Nothing, Nothing)
                Return
            End If

            'プロパティの型チェック
            Dim t As Type = PropertyReader.GetActualPropertyType(prop)

            '数値型の場合、型変換
            If val.GetType.Equals(t) = False AndAlso IsNumeric(val) Then
                If t.Equals(GetType(Boolean)) AndAlso (val.ToString = "0" OrElse val.ToString = "1") Then
                    '#97　MySQL対応
                    val = If(val.ToString = "0", False, True)
                Else
                    Select Case t
                        Case GetType(Integer) : val = Integer.Parse(val)
                        Case GetType(Decimal) : val = Decimal.Parse(val)
                        Case GetType(Long) : val = Long.Parse(val)
                        Case GetType(Double) : val = Double.Parse(val)
                        Case GetType(SByte) : val = SByte.Parse(val)
                        Case GetType(Short) : val = Short.Parse(val)
                    End Select
                End If
            End If

            prop.SetValue(model, val, Nothing)
        Catch ex As Exception
            Dim msg As String
            If val IsNot Nothing Then
                msg = String.Format("プロパティ代入に失敗しました。Class={0}, Property={1}, Value={2}", model.GetType.Name, prop.Name, val)
            Else
                msg = String.Format("プロパティ代入に失敗しました。Class={0}, Property={1}, Value Is Nothing ", model.GetType.Name, prop.Name)
            End If
            Throw New InvalidOperationException(msg, ex)
        End Try
    End Sub

End Class
